package com.lagou.web.framework.util;

import java.io.File;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

public class ClazzUtils {
    private static final String CLASS_SUFFIX = ".class";
    private static final String CLASS_FILE_PREFIX = File.separator + "classes" + File.separator;
    private static final String PACKAGE_SEPARATOR = ".";

    /**
     * 根据类名创建class
     *
     * @param className
     * @return Object
     */
    public static Class<?> getClass(String className) {
        try {
            return Class.forName(className);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 根据类创建对象
     *
     * @param c
     * @return Object
     */
    public static Object getObject(Class<?> c) {
        try {
            return c.newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 查找包下的所有类的名字
     *
     * @param packageName 包名
     * @return List集合，内容为类的全名
     */
    public static List<Class<?>> loadClass(String packageName) {
        return loadClass(packageName, true);
    }

    /**
     * 查找包下的所有类的名字
     *
     * @param packageName      包名
     * @param readChildPackage 是否读取子包内容
     * @return List集合，内容为类的全名
     */
    public static List<Class<?>> loadClass(String packageName, boolean readChildPackage) {
        List<Class<?>> result = new ArrayList<>();
        String suffixPath = packageName.replaceAll("\\.", "/");
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        try {
            Enumeration<URL> urls = loader.getResources(suffixPath);
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                if (url != null) {
                    String protocol = url.getProtocol();
                    if ("file".equals(protocol)) {
                        result.addAll(getAllClassByFile(new File(url.getPath()), readChildPackage));
                    } else if ("jar".equals(protocol)) {
                        JarFile jarFile = null;
                        try {
                            jarFile = ((JarURLConnection) url.openConnection()).getJarFile();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                        if (jarFile != null) {
                            result.addAll(getAllClassByJar(jarFile, packageName, readChildPackage));
                        }
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 获取所有class
     *
     * @param file
     * @param readChildPackage 是否读取子文件夹
     * @return List
     */
    private static List<Class<?>> getAllClassByFile(File file, boolean readChildPackage) {
        return getAllClassByFile(file, false, readChildPackage);
    }

    /**
     * 递归获取所有class
     *
     * @param file
     * @param isChildPackage   是否为子文件夹
     * @param readChildPackage 是否读取子文件夹
     * @return List
     */
    private static List<Class<?>> getAllClassByFile(File file, boolean isChildPackage, boolean readChildPackage) {
        List<Class<?>> result = new ArrayList<>();
        if (!file.exists()) {
            return result;
        }
        if (file.isFile()) {
            String path = file.getPath();
            // 注意：这里替换文件分割符要用replace。因为replaceAll里面的参数是正则表达式,而windows环境中File.separator="\\"的,因此会有问题
            if (path.endsWith(CLASS_SUFFIX)) {
                path = path.replace(CLASS_SUFFIX, "");
                // 从"/classes/"后面开始截取
                String clazzName = path.substring(path.indexOf(CLASS_FILE_PREFIX) + CLASS_FILE_PREFIX.length()).replace(File.separator, PACKAGE_SEPARATOR);
                if (clazzName.indexOf("$") == -1) {
                    result.add(getClass(clazzName));
                }
            }
        } else {
            if (!isChildPackage || isChildPackage && readChildPackage) {
                File[] listFiles = file.listFiles();
                if (listFiles != null && listFiles.length > 0) {
                    for (File f : listFiles) {
                        result.addAll(getAllClassByFile(f, true, readChildPackage));
                    }
                }
            }
        }
        return result;
    }

    /**
     * 递归获取jar所有class文件
     *
     * @param jarFile
     * @param packageName      包名
     * @param readChildPackage 是否需要迭代遍历
     * @return List
     */
    private static List<Class<?>> getAllClassByJar(JarFile jarFile, String packageName, boolean readChildPackage) {
        List<Class<?>> result = new ArrayList<>();
        Enumeration<JarEntry> entries = jarFile.entries();
        while (entries.hasMoreElements()) {
            JarEntry jarEntry = entries.nextElement();
            String name = jarEntry.getName();
            // 判断是不是class文件
            if (name.endsWith(CLASS_SUFFIX)) {
                name = name.replace(CLASS_SUFFIX, "").replace("/", ".");
                if (readChildPackage) {
                    // 如果要子包的文件,那么就只要开头相同且不是内部类就ok
                    if (name.startsWith(packageName) && name.indexOf("$") == -1) {
                        result.add(getClass(name));
                    }
                } else {
                    // 如果不要子包的文件,那么就必须保证最后一个"."之前的字符串和包名一样且不是内部类
                    if (packageName.equals(name.substring(0, name.lastIndexOf("."))) && name.indexOf("$") == -1) {
                        result.add(getClass(name));
                    }
                }
            }
        }
        return result;
    }
}